package com.iwdnb.gkgz.application.strategy;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.alibaba.fastjson.JSON;

import cn.hutool.core.date.DateUtil;
import com.google.common.collect.Lists;
import com.iwdnb.bmnf.common.service.CacheService;
import com.iwdnb.bmnf.common.utils.BooleanUtils;
import com.iwdnb.gkgz.application.model.request.AddStrategyTradeStockDataRequest;
import com.iwdnb.gkgz.application.service.StockRangeService;
import com.iwdnb.gkgz.application.service.StockService;
import com.iwdnb.gkgz.application.utils.RedGreenLightUtils;
import com.iwdnb.gkgz.common.model.dto.Stock;
import com.iwdnb.gkgz.common.model.dto.StockDayData;
import com.iwdnb.gkgz.common.model.dto.StockRangeDTO;
import com.iwdnb.gkgz.common.model.dto.StrategyTradeDTO;
import com.iwdnb.gkgz.common.utils.BigDecimalUtils;
import com.iwdnb.gkgz.common.utils.DateUtils;
import com.iwdnb.gkgz.common.utils.StockBlackListUtils;
import com.iwdnb.gkgz.common.utils.StockRangeUtils;
import com.iwdnb.gkgz.common.utils.StockUtils;
import com.iwdnb.gkgz.common.utils.WorkDayUtils;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockDayDataRepository;
import com.iwdnb.gkgz.infrastructure.dao.repo.StockRepository;
import com.iwdnb.service.router.annotation.Router;
import com.iwdnb.wwzy.dao.MetaDataDAO;
import com.iwdnb.wwzy.model.query.MetaQuery;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import static com.iwdnb.gkgz.common.constant.CacheConstants.STOCK_STRATEGE_CACHE_PREFIX;
import static com.iwdnb.gkgz.common.constant.GkgzConstants.DEFAULT_START_DATE;
import static com.iwdnb.gkgz.common.constant.GkgzConstants.OBJECT_CODE_STRATEGY_TRADE;
import static com.iwdnb.gkgz.common.utils.StockUtils.getAveragePrice;

@Service
@Slf4j
@Router(interfaceClass = StockBackValidateService.class)
public class CommmonStockBackValidateService implements StockBackValidateService {
    //protected Executor executor = ExecutorUtils.getExecutor();

    @Autowired
    protected StockRepository stockRepository;
    @Autowired
    protected StockDayDataRepository stockDayDataRepository;
    @Autowired
    protected MetaDataDAO metaDataDAO;
    @Autowired
    protected StockRangeService stockRangeService;
    @Autowired
    protected CacheService cacheService;
    @Autowired
    protected Executor executor;
    protected int fivePeriod = 5;
    protected int tenPeriod = 10;
    protected int thirteenPeriod = 13;
    protected int twentyPeriod = 20;
    protected int thirtyPeriod = 30;
    protected int sixtyPeriod = 60;
    protected List<Integer> ALL_PERIODS = Lists.newArrayList(twentyPeriod, thirteenPeriod, tenPeriod, sixtyPeriod,
        thirtyPeriod);
    protected Map<Integer, String> PERIOD_MAP = new LinkedHashMap<>();
    protected BigDecimal twoHundred = new BigDecimal(200);
    protected BigDecimal oneHundred = new BigDecimal(100);

    protected BigDecimal ninety = new BigDecimal(90);
    protected BigDecimal eighty = new BigDecimal(80);
    protected BigDecimal seventy = new BigDecimal(70);
    protected BigDecimal sixty = new BigDecimal(60);
    protected BigDecimal fifty = new BigDecimal(50);
    protected BigDecimal fourtyFive = new BigDecimal(45);
    protected BigDecimal fourty = new BigDecimal(40);
    protected BigDecimal thirtyFive = new BigDecimal(35);
    protected BigDecimal thirty = new BigDecimal(30);
    protected BigDecimal twenty = new BigDecimal(20);
    protected BigDecimal seventeen = new BigDecimal(17);
    protected BigDecimal fifteen = new BigDecimal(15);
    protected BigDecimal fourteen = new BigDecimal(14);
    protected BigDecimal thirteen = new BigDecimal(13);
    protected BigDecimal twelve = new BigDecimal(12);
    protected BigDecimal eleven = new BigDecimal(11);
    protected BigDecimal ten = new BigDecimal(10);
    protected BigDecimal nine = new BigDecimal(9);
    protected BigDecimal eight = new BigDecimal(8);
    protected BigDecimal seven = new BigDecimal(7);
    protected BigDecimal six = new BigDecimal(6);
    protected BigDecimal five = new BigDecimal(5);
    protected BigDecimal four = new BigDecimal(4);
    protected BigDecimal three = new BigDecimal(3);
    protected BigDecimal two = new BigDecimal(2);
    protected BigDecimal one = new BigDecimal(1);
    protected BigDecimal zero = new BigDecimal(0);

    protected BigDecimal pointFive = new BigDecimal(0.5);

    public void init() {
        for (Integer period : ALL_PERIODS) {
            PERIOD_MAP.put(period, period + "日均线");
        }

    }

    @Override
    public void doAllBackValidate(AddStrategyTradeStockDataRequest request) {
        init();
        log.debug("doAllBackValidate,request:" + JSON.toJSONString(request));
        List<Stock> stockList = stockRepository.findAll();
        stockList = stockList.stream().filter(t -> BooleanUtils.isTrue(t.getValidFlag())).collect(Collectors.toList());
        Map<String, Stock> stockMap = stockList.stream().collect(
            Collectors.toMap(t -> t.getCode(), Function.identity()));
        String codeStr = request.getCodes();
        List<String> codes = null;
        if (StringUtils.isNotBlank(codeStr)) {
            codes = Lists.newArrayList(codeStr.split(","));
        } else {
            codes = stockList.stream()
                .map(stock ->  stock.getCode()).collect(Collectors.toList());
            codes = StockBlackListUtils.getCanBuyCodeList(codes);
            //随机选股票
            if (BooleanUtils.isTrue(request.getRandom())) {
                List<String> list = new ArrayList<>();
                Random r = new Random();
                int temp = 0;
                for (int i = 0; i < request.getEnd(); i++) {
                    temp += r.nextInt(10);
                    list.add(codes.get(temp));
                }
                codes = list;
            } else {
                //分页选股
                int end = request.getEnd() > codes.size() ? codes.size() : request.getEnd();
                codes = codes.subList(request.getBegin(), end);
            }
        }
        log.debug("共计{}只股票", codes.size());
        log.debug("股票代码:{}", StringUtils.join(codes, ","));
        AtomicInteger count = new AtomicInteger(0);
        String cacheKey = null;
        for (int i = 0; i < codes.size(); i++) {
            String code = codes.get(i);
            if (request.isCacheFlag()) {
                cacheKey = STOCK_STRATEGE_CACHE_PREFIX + request.getUuid() + ":" + code;
                String cacheFlag = cacheService.get(cacheKey);
                if (StringUtils.isNotBlank(cacheFlag)) {
                    log.debug("{}-已做过策略，被缓存,跳过", code);
                    continue;
                }
            }
            int finalIndex = i;
            int finalLastIndex = codes.size() - 1;
            String finalCacheKey = cacheKey;
            final BigDecimal MIN_PRICE = new BigDecimal("0.5");
            executor.execute(() -> {
                List<StockDayData> stockDayDataList = stockDayDataRepository.queryAll(code);
                if (CollectionUtils.isEmpty(stockDayDataList)) {
                    return;
                }
                for (StockDayData data : stockDayDataList) {
                    if (BigDecimalUtils.isLt(data.getClosePrice(), MIN_PRICE)) {
                        log.debug("{}-股价存在股价小于0.5元的日期,跳过", code);
                        Stock stock = stockMap.get(code);
                        stock.setValidFlag(BooleanUtils.FALSE);
                        stockRepository.save(stock);
                        return;
                    }
                }
                List<StrategyTradeDTO> strategyTradeDTOList = queryBackValidateDateList(code, request.getStrategy(),
                    stockDayDataList, request);
                if (CollectionUtils.isEmpty(strategyTradeDTOList)) {
                    log.debug("{}-没有可以买入的日期,跳过", code);
                    return;
                }
                if (BooleanUtils.isFalse(request.getBackFlag())) {
                    return;
                }
                List<String> dateList = strategyTradeDTOList.stream().map(o -> DateUtil.formatDate(o.getBuyDate()))
                    .collect(Collectors.toList());
                log.debug("{}-{}-匹配日期:{}", code, finalIndex, dateList);
                doBackValidate(code, request.getStrategy(), stockDayDataList, strategyTradeDTOList);
                if (CollectionUtils.isNotEmpty(strategyTradeDTOList)) {
                    strategyTradeDTOList = strategyTradeDTOList.stream().filter(
                            strategyTradeDTO -> "sell".equals(strategyTradeDTO.getStatus()))
                        .collect(Collectors.toList());
                    if (CollectionUtils.isNotEmpty(strategyTradeDTOList)) {
                        if (BooleanUtils.isTrue(request.getStoreFlag())) {
                            if (BooleanUtils.isFalse(request.getGenerateFlag())) {
                                metaDataDAO.update(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTOList);
                            } else {
                                metaDataDAO.insert(OBJECT_CODE_STRATEGY_TRADE, strategyTradeDTOList);
                            }
                        }
                        count.addAndGet(strategyTradeDTOList.size());
                        log.debug("新增{}条交易记录,共{}条", strategyTradeDTOList.size(), count.get());
                        if (request.isCacheFlag()) {
                            cacheService.put(finalCacheKey, BooleanUtils.TRUE, 86400 * 365);
                        }
                    }
                }
                if (finalIndex == finalLastIndex) {
                    log.info("{}策略执行完成,共产生{}条记录:", request.getStrategy(), count.get());
                }
            });

        }
    }

    public List<StrategyTradeDTO> queryBackValidateDateList(String code, String strategy,
        List<StockDayData> stockDayDataList,
        AddStrategyTradeStockDataRequest request) {
        return null;
    }

    @Override
    public void doBackValidate(String code, String strategy,
        List<StockDayData> stockDayDataList, List<StrategyTradeDTO> strategyTradeDTOList) {
        Date latestEndDate = strategyTradeDTOList.get(0).getBuyDate();
        latestEndDate = DateUtil.offsetDay(latestEndDate, -1);
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            Date d = strategyTradeDTO.getBuyDate();
            if (DateUtils.beforeOrEquals(d, latestEndDate)) {
                log.debug("{}-{}在上一个周期内,忽略", code, d);
                continue;
            }
            //单交易回测
            doBackTradeInfo(code, stockDayDataList, strategyTradeDTO);
            latestEndDate = strategyTradeDTO.getSellDate();
        }
    }

    @Override
    public void doCaculateBackValidate(String strategy, int buyBatchNum, String multipleFlag) {
        MetaQuery metaQuery = new MetaQuery();
        metaQuery.addParam("uuid", strategy);
        metaQuery.addParam("status", "sell");
        List<StrategyTradeDTO> strategyTradeDTOList = metaDataDAO.queryListByCondition(OBJECT_CODE_STRATEGY_TRADE,
            metaQuery, StrategyTradeDTO.class);
        if (CollectionUtils.isEmpty(strategyTradeDTOList)) {
            log.warn("没有策略交易记录。");
            return;
        }

        Collections.shuffle(strategyTradeDTOList);
        Date date = DateUtil.parseDate(DEFAULT_START_DATE);
        int availableNum = 0;
        StockProfitRate stockProfitRate = new StockProfitRate();
        stockProfitRate.setProfitRate(new BigDecimal(100));
        stockProfitRate.setTradeList(new ArrayList<>());
        stockProfitRate.setTradeCount(0);
        stockProfitRate.setHoldDay(0);
        Date now = new Date();
        for (int i = 0; i < 10000; i++) {
            date = WorkDayUtils.getNextWorkDay(date);
            if (date.after(now)) {
                break;
            }
            //持有股票不为空,计算是否卖出
            if (CollectionUtils.isNotEmpty(stockProfitRate.getTradeList())) {
                caculateTrade(stockProfitRate, date, buyBatchNum, multipleFlag);
            }
            availableNum = buyBatchNum - stockProfitRate.getTradeList().size();
            if (availableNum > 0) {
                //红灯，不买入
                //if (!RedGreenLightUtils.isAvailable(DateUtil.formatDate(date))) {
                //    continue;
                //}
                List<StrategyTradeDTO> buyStockList = getBuyStockList(strategyTradeDTOList, date, availableNum);
                if (CollectionUtils.isEmpty(buyStockList)) {
                    continue;
                }
                stockProfitRate.getTradeList().addAll(buyStockList);
            }
        }
        log.debug("总交易次数:{},持有天数:{},总收益(含本金):{}", stockProfitRate.getTradeCount(), stockProfitRate.getHoldDay(),
            stockProfitRate.getProfitRate());
    }

    /**
     * 持有股票不为空,计算是否卖出
     *
     * @param stockProfitRate
     * @param date
     * @param buyBatchNum
     * @param multipleFlag
     * @return
     */
    private void caculateTrade(StockProfitRate stockProfitRate, Date date, int buyBatchNum, String multipleFlag) {
        String dateStr = DateUtil.formatDate(date);
        List<StrategyTradeDTO> remainList = new ArrayList<>();
        BigDecimal rate = stockProfitRate.getProfitRate();
        int tradeCount = stockProfitRate.getTradeCount();
        int holdDay = stockProfitRate.getHoldDay();
        for (StrategyTradeDTO strategyTradeDTO : stockProfitRate.getTradeList()) {
            if (DateUtil.formatDate(strategyTradeDTO.getSellDate()).equals(dateStr)) {
                log.debug("{}-{}买入,{}卖出，持有{}天,收益:{}", strategyTradeDTO.getCode(),
                    DateUtil.formatDate(strategyTradeDTO.getBuyDate()),
                    DateUtil.formatDate(strategyTradeDTO.getSellDate()), strategyTradeDTO.getHoldDay(),
                    strategyTradeDTO.getProfitRate());
                BigDecimal profitRate = BigDecimalUtils.divide(strategyTradeDTO.getProfitRate(),
                    new BigDecimal(buyBatchNum));

                //叠乘
                if (BooleanUtils.isTrue(multipleFlag)) {
                    profitRate = BigDecimalUtils.add(new BigDecimal(100), profitRate);
                    profitRate = BigDecimalUtils.divide(profitRate, new BigDecimal(100));
                    rate = BigDecimalUtils.multiply(rate, profitRate);
                } else {
                    //累加
                    rate = rate.add(profitRate);
                }
                log.debug("累计收益:{}", rate);
                tradeCount++;
                holdDay += strategyTradeDTO.getHoldDay();
            } else {
                remainList.add(strategyTradeDTO);
            }
        }
        stockProfitRate.setTradeList(remainList);
        stockProfitRate.setProfitRate(rate);
        stockProfitRate.setTradeCount(tradeCount);
        stockProfitRate.setHoldDay(holdDay);
    }

    /**
     * 买入股票
     *
     * @param strategyTradeDTOList
     * @param date
     * @param availableNum
     * @return
     */
    private List<StrategyTradeDTO> getBuyStockList(List<StrategyTradeDTO> strategyTradeDTOList, Date date,
        int availableNum) {
        String dateStr = DateUtil.formatDate(date);
        int count = 0;
        List<StrategyTradeDTO> buyList = new ArrayList<>();
        for (StrategyTradeDTO strategyTradeDTO : strategyTradeDTOList) {
            if (DateUtil.formatDate(strategyTradeDTO.getBuyDate()).equals(dateStr)) {
                buyList.add(strategyTradeDTO);
                count++;
                if (count == availableNum) {
                    return buyList;
                }
            }
        }
        return buyList;
    }

    private void doBackTradeInfo(String code, List<StockDayData> stockDayDataList,
        StrategyTradeDTO strategyTradeDTO) {
        String date = DateUtil.formatDate(strategyTradeDTO.getBuyDate());
        StockDayData buyDayData = StockUtils.getStockDayData(stockDayDataList, date);
        BigDecimal buyPrice = buyDayData.getClosePrice();
        List<StockDayData> subList = StockUtils.getStockDayDataListAfterDate(stockDayDataList, date, 100);
        if (CollectionUtils.isEmpty(subList) || subList.size() < 10) {
            return;
        }
        int size = subList.size();
        BigDecimal closeRate = null;
        String sellDate = null;
        String info = "";
        BigDecimal sellPrice = null;
        BigDecimal rate = closeRate;
        int holdIndex = 0;
        BigDecimal rangeMaxPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeMaxRate = BigDecimal.ZERO;
        BigDecimal rangeMinPrice = strategyTradeDTO.getBuyPrice();
        BigDecimal rangeMinRate = BigDecimal.ZERO;
        for (int i = 0; i < 100; i++) {
            if (i == size) {
                return;
            }
            StockDayData data = subList.get(i);
            StockDayData yesterdayDayData = i == 0 ? data : subList.get(i - 1);
            BigDecimal closePrice = data.getClosePrice();
            closeRate = BigDecimalUtils.subStractAndDividePrecent(closePrice, buyPrice);
            sellDate = data.getDate();
            sellPrice = data.getClosePrice();
            holdIndex = i;
            BigDecimal upRate = BigDecimalUtils.subStractAndDividePrecent(data.getMaxPrice(), buyPrice);
            BigDecimal openRange = data.getOpenPrice().subtract(buyPrice);
            BigDecimal openRate = BigDecimalUtils.divideToPrecent(openRange, buyPrice);
            rangeMaxPrice = BigDecimalUtils.isGt(data.getMaxPrice(), rangeMaxPrice) ? data.getMaxPrice()
                : rangeMaxPrice;
            rangeMaxRate = BigDecimalUtils.subStractAndDividePrecent(rangeMaxPrice, buyPrice);
            rangeMinPrice = BigDecimalUtils.isLt(data.getMinPrice(), rangeMinPrice) ? data.getMinPrice()
                : rangeMinPrice;
            rangeMinRate = BigDecimalUtils.subStractAndDividePrecent(rangeMinPrice, buyPrice);
            BigDecimal realtimeRate = BigDecimalUtils.subStractAndDividePrecent(data.getClosePrice(), buyPrice);
            rate = closeRate;
            String sellInfo = "收盘价卖出";

            // 跌6个点，跑路
            if (BigDecimalUtils.isLt(closeRate, new BigDecimal(-6))) {
                sellInfo = "跌了超过6个点,止损";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            //120个点以上特殊处理
            if (BigDecimalUtils.isGe(realtimeRate, new BigDecimal(120))) {
                Integer yesterdayTradeNumTenTimes = yesterdayDayData.getTradeNum() * 10;
                if (data.getTradeNum() >= yesterdayTradeNumTenTimes) {
                    sellInfo = "出现十倍信号量,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }
            //50个点以上特殊处理
            if (BigDecimalUtils.isGe(realtimeRate, new BigDecimal(50))) {
                Integer yesterdayTradeNum = yesterdayDayData.getTradeNum() * 10;
                if (data.getTradeNum() >= yesterdayTradeNum) {
                    sellInfo = "出现十倍信号量,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate,
                        openRate,
                        closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
                Integer yesterdayTradeNumThreeTimes = yesterdayDayData.getTradeNum() * 3;
                if (data.getTradeNum() >= yesterdayTradeNumThreeTimes && BigDecimalUtils.isLe(data.getRate(),
                    new BigDecimal(-5))) {
                    sellInfo = "出现3倍信号量且暴跌,止盈";
                    log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo,
                        rate, openRate, closeRate, upRate);
                    info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                        + ",最高点收益=" + upRate;
                    break;
                }
            }
            //回落超过3成
            if (i > 2
                && BigDecimalUtils.isGe(rangeMaxRate, new BigDecimal(10))
                && BigDecimalUtils.isGe(rangeMaxRate.multiply(new BigDecimal(0.7)), realtimeRate)) {
                sellInfo = "回落3成,卖出";
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate, closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }
            if (i == 99) {
                log.debug("第{}天-{}-{}-{}，收益={}，开盘收益={},收盘收益={},最高点收益={}", (i + 1), code, data.getDate(), sellInfo, rate,
                    openRate,
                    closeRate, upRate);
                info += "第" + (i + 1) + "天" + sellInfo + ",收益=" + rate + ",开盘收益=" + openRate + ",收盘收益=" + closeRate
                    + ",最高点收益=" + upRate;
                break;
            }

        }
        strategyTradeDTO.setHoldDay(holdIndex + 1);
        strategyTradeDTO.setSellDate(DateUtil.parseDate(sellDate));
        strategyTradeDTO.setSellPrice(sellPrice);
        strategyTradeDTO.setProfitRate(rate);
        strategyTradeDTO.setRangeMaxPrice(rangeMaxPrice);
        strategyTradeDTO.setRangeMaxRate(rangeMaxRate);
        strategyTradeDTO.setRangeMinPrice(rangeMinPrice);
        strategyTradeDTO.setRangeMinRate(rangeMinRate);
        strategyTradeDTO.setStrategyDescription(info);
        strategyTradeDTO.setStatus("sell");
        strategyTradeDTO.setWinFlag(BigDecimalUtils.isGt(rate, BigDecimal.ZERO) ? "y" : "n");
    }

    protected List<StockRangeDTO> getRangeList(String code, List<StockDayData> stockDayDataList) {
        String cacheKey = "stockRange:" + code;
        //String content = cacheService.get(cacheKey);
        //if (StringUtils.isNotBlank(content)) {
        //    return JSON.parseArray(content, StockRangeDTO.class);
        //}
        List<StockRangeDTO> stockRangeDTOList = StockRangeUtils.calculateStockRange(stockDayDataList);
        cacheService.put(cacheKey, JSON.toJSONString(stockRangeDTOList), 86400 * 365);
        return stockRangeDTOList;
    }

    @Data
    public static class StockProfitRate {
        private BigDecimal profitRate;

        private Integer tradeCount;

        private Integer holdDay;

        private List<StrategyTradeDTO> tradeList;
    }
}
